// Copyright (c) [Year] [name of copyright holder]
// [Software Name] is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2. 
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2 
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
// See the Mulan PSL v2 for more details.

/*!
 * \autor captainpeng
 * \date 2020-1-7
 * \version 1.0
 * \copyright
 */

#ifndef MY_UFILE_HPP
#define MY_UFILE_HPP

#include"mystd.hpp"

namespace my{

    class ufile{
    public:
        using charType = char;
        using chlib = my::charLib<charType>;
        using strType = std::basic_string<charType>;
        using streamType = std::basic_ifstream<charType>;
        using strstreamType = std::basic_stringstream<charType>;

        using unicodeType = unicode_t;

    private:
        static const int csize = 360; // 5760;
        
        std::string mName;
        std::FILE * mFp;
        charType * mBuf;
        int mCur;
        int mCount;

        // load 是用来加载文件中的字符
        bool load(){
            int i = 0;
            // 1. 如果缓存中还有其他字符，则把他们挪到最前面
            //    需要保证的是 i 一定是下一个要加载的位置
            for(; mBuf[mCur] != '\0'; ++mCur, ++i){
                mBuf[i] = mBuf[mCur];
            }
            mCur = 0;
            
            // 2. 如果文件已经没有可读的，就把位置 i 的设为 '\0'
            if(mFp == nullptr || std::feof(mFp) != 0){
                mBuf[i] = '\0';
                return false;
            }

            // 3. 从位置 i 开始读字符，那必然总大小要减去 i
            //    读取后把 mCur 重置
            int count = chlib::fread(mFp, &mBuf[i], csize - i - 1);
            my::errExit(std::ferror(mFp) == 0, "my::ufile: %s", mName.c_str());
            mBuf[i + count] = '\0';
            return count != 0;
        }
        
    public:
        ufile():
            mName(),
            mFp(nullptr),
            mBuf(nullptr),
            mCur(-1),
            mCount(0)
            {}

        ufile(const std::string & filename):
            mName(),
            mFp(nullptr),
            mBuf(nullptr),
            mCur(-1),
            mCount(0)
            {
                this->open(filename);
            }
        
        ~ufile(){
            close();
        }

        bool open(const std::string & filename){
            mFp = std::fopen(filename.c_str(), "r");
            my::errExit(mFp != nullptr, "my::ufile::open(\"%s\")", filename.c_str());
                        
            mName = filename;
            // std::fwide(mFp, sizeof(charType) == sizeof(wchar_t)? 1:-1);
            if(mBuf == nullptr) mBuf = memory::alloc<charType>(csize);
            
            mBuf[0] = '\0';
            mCur = 0;
            
            load();
            mCount = 0;
            return true;
        }

        bool is_open(){
            return mFp != nullptr && mBuf != nullptr && mCur >= 0;
        }

        bool good(){
            return mCur != -1;
        }
        
        bool eof(){
            return (mFp == nullptr || std::feof(mFp)) && (mCur < 0 || mBuf[mCur] == '\0');
        }

        bool error(){
            return mCur == -1 || mFp == nullptr || std::ferror(mFp) != 0;
        }

        ufile & read(my::buffer<unicodeType> & buf){
            return read(buf.begin(), buf.size());
        }

        // read 函数是用来转换成 unicode 数组的
        ufile & read(unicodeType * buf, int size){
            if((!is_open()) || size <= 0) return *this;

            int step = 0;
            unicodeType ui;
            
            int i = 0;
            // 1. 当 buf 满了或文件读到 eof 时候结束
            //    若其中发生了字符缺失或者不合法的字符时候
            //    应该按照不同情况进行处理，目前:
            //    unicode::error 尝试放弃该字段并向前读取，还需要往 buf 里面再填充字符
            //    unicode::lack 尝试重新加载，然后再次读取
            
            while(i < size){
                if(mBuf[mCur] == '\0' && !load()) break;
                
                ui = utf8::CStrToUnicode(&mBuf[mCur], step);

                if(ui == unicode::lack){
                    if(mCur != 0){
                        load();
                        continue;
                    }else
                        ui = unicode::replace;
                }else if(ui == unicode::error){
                    step = 1;
                    ui = unicode::replace;
                }
                
                buf[i] = ui;
                ++i;
                mCur += step;
            }
            mCount = i;

            return *this;
        }

        int gcount(){
            return mCount;
        }

        const std::string & filename(){
            return mName;
        }

        void close(){
            if(mFp != nullptr){
                std::fclose(mFp);
                mFp = nullptr;
            }
            
            memory::free(mBuf);
            mBuf = nullptr;
            mCur = -1;
            mCount = 0;
        }

        charType * data(){
            return mBuf;
        }
    };
}

#endif
